package com.pocket.demo.elasticsearch.service;

import cn.hutool.core.collection.CollectionUtil;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.json.JsonData;
import com.pocket.demo.business.controller.vo.HotelPageReqVO;
import com.pocket.demo.elasticsearch.dao.HotelDocDao;
import com.pocket.demo.elasticsearch.doc.HotelDoc;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
import org.springframework.data.elasticsearch.client.elc.NativeQueryBuilder;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.query.HighlightQuery;
import org.springframework.data.elasticsearch.core.query.highlight.Highlight;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightField;
import org.springframework.data.elasticsearch.core.query.highlight.HighlightFieldParameters;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author cookie. 【demo-elasticsearch-2.3】
 **/
@Service
public class HotelDocService {

    @Resource
    private HotelDocDao hotelDocDao;

    @Resource
    private ElasticsearchTemplate restTemplate;

    @Resource
    private ElasticsearchOperations operations;

    /**
     * 判断 hotel 索引是否存在
     * @return
     */
    public boolean isIndexExist(){
        return restTemplate.indexOps(HotelDoc.class).exists();
    }

    public boolean deleteIndex(){
        return restTemplate.indexOps(HotelDoc.class).delete();
    }

    public void createIndex(boolean isReplace){
        IndexOperations indexOperations = restTemplate.indexOps(HotelDoc.class);
        if(!indexOperations.exists()){
            // 设置映射: 在正式开发中，几乎不会使用框架创建索引或设置映射，这是架构或者管理员的工作，不适合使用代码实现
            restTemplate.indexOps(HotelDoc.class).putMapping();
            indexOperations.create();
        } else if(isReplace){
            indexOperations.delete();
            indexOperations.create();
        }
    }

    /**
     * 【demo-elasticsearch-4】
     *  TODO 最新版本网上资料少，官网介绍好简单，靠 API 语法提示拼接的条件，感觉好复杂，难道是用错了？
     * @param pageReqVO
     * @return
     */
    public SearchPage<HotelDoc> searchEs(HotelPageReqVO pageReqVO) {

        List<Query> mustQueryList = new ArrayList<>();
        // term 查询
        if (StringUtils.isNotBlank(pageReqVO.getBrand())){
            mustQueryList.add(new Query(new TermQuery.Builder().field("brand").value(pageReqVO.getBrand()).build()));
        }
        if (StringUtils.isNotBlank(pageReqVO.getCity())){
            mustQueryList.add(new Query(new TermQuery.Builder().field("city").value(pageReqVO.getCity()).build()));
        }
        if (StringUtils.isNotBlank(pageReqVO.getStarName())){
            mustQueryList.add(new Query(new TermQuery.Builder().field("starName").value(pageReqVO.getStarName()).build()));
        }
        if (StringUtils.isNotBlank(pageReqVO.getBusiness())){
            mustQueryList.add(new Query(new TermQuery.Builder().field("business").value(pageReqVO.getBusiness()).build()));
        }

        // match 查询
        if (StringUtils.isNotBlank(pageReqVO.getName())){
            mustQueryList.add(new Query(new MatchQuery.Builder().field("name").query(pageReqVO.getName()).build()));
        }

        List<Query> filterQueryList = new ArrayList<>();
        // range 查询
        if (pageReqVO.getMaxPrice() != null){
            filterQueryList.add(new Query(new RangeQuery.Builder().field("price").lte(JsonData.of(pageReqVO.getMaxPrice())).build()));
        }
        if (pageReqVO.getMinPrice() != null){
            filterQueryList.add(new Query(new RangeQuery.Builder().field("price").gte(JsonData.of(pageReqVO.getMinPrice())).build()));
        }

        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        if (CollectionUtil.isNotEmpty(mustQueryList)){
            boolQueryBuilder.must(mustQueryList);
        }
        if (CollectionUtil.isNotEmpty(filterQueryList)){
            boolQueryBuilder.filter(filterQueryList);
        }

        // bool 查询
        NativeQueryBuilder queryBuilder = NativeQuery.builder().withQuery(new Query(boolQueryBuilder.build()));

        // 高亮
        if (CollectionUtil.isNotEmpty(pageReqVO.getHighlightFields())){
            List<HighlightField> fields = pageReqVO.getHighlightFields().stream().map(
                    item -> new HighlightField(item, HighlightFieldParameters.builder().withPreTags("<em>").withPostTags("</em>").build())
            ).collect(Collectors.toList());
            queryBuilder.withHighlightQuery(new HighlightQuery(new Highlight(fields), HotelDoc.class));
        }

        // 排序
        if (CollectionUtil.isNotEmpty(pageReqVO.getOrders())){
            List<SortOptions> values = pageReqVO.getOrders().stream().map(
                    item -> SortOptions.of(s -> s.field(builder -> builder.field(item.getField()).order(SortOrder.valueOf(StringUtils.capitalize(item.getOrder().toLowerCase())))))
            ).collect(Collectors.toList());
            queryBuilder.withSort(values);
        }

        // 分页
        Pageable pageable = null;
        if (pageReqVO.getPageNo() != null && pageReqVO.getPageSize() != null){
            pageable = PageRequest.of(pageReqVO.getPageNo(), pageReqVO.getPageSize());
            queryBuilder.withPageable(pageable);
        }
        SearchHits<HotelDoc> searchHits = operations.search(queryBuilder.build(), HotelDoc.class);

        return SearchHitSupport.searchPageFor(searchHits, pageable);
    }

}
